using System;
using System.Collections.Generic;
using System.Reflection;

namespace Fadd.Collections
{
    /// <summary>
    /// Used to keep track of generic interfaces in a single list.
    /// </summary>
    /// <example>
    /// <code>
    /// <![CDATA[
    /// public class EventBroker
    /// {
    ///     GenericSubscriberList _subscribers;
    /// 
    ///     public EventBroker()
    ///     {
    ///         _subscriberS = new GenericSubscriberList(typeof(IEventConsumer<>));
    ///     }
    /// 
    ///     public void Subscribe<T>(IEventConsumer<T> consumer) where T : class, new()
    ///     {
    ///         _subscribers.Add<T>(consumer);
    ///     }
    /// 
    ///     public void Invoke(object value)
    ///     {
    ///         _subscribers.Invoke(value);
    ///     }
    /// }
    /// ]]>
    /// </code>
    /// </example>
    public class GenericSubscriberList
    {
        private readonly Dictionary<Type, Wrapper> _items = new Dictionary<Type, Wrapper>();
        private readonly Type _genericType;


        /// <summary>
        /// Initializes a new instance of the <see cref="GenericSubscriberList"/> class.
        /// </summary>
        /// <param name="genericType">Type of the generic.</param>
        public GenericSubscriberList(Type genericType)
        {
            _genericType = genericType;    
        }

        /// <summary>
        /// Invokes all subscribers for the specified object.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns><c>true</c> if one or more subscribers were found; otherwise <c>false</c>.</returns>
        public bool Invoke(object value)
        {
            Type targetType = value.GetType();
            Wrapper wrapper;

            if (!_items.TryGetValue(targetType, out wrapper))
                return false;

            wrapper.Invoke(value, e => true);
            return true;
        }

        /// <summary>
        /// Register a consumer
        /// </summary>
        /// <typeparam name="T">Type that the consumer is for</typeparam>
        /// <param name="consumer">Consumer to add</param>
        public void Register<T>(object consumer)
        {
            Type targetType = typeof (T);
            Wrapper wrapper;

            if (!_items.TryGetValue(targetType, out wrapper))
            {
                wrapper = new Wrapper(_genericType, targetType);
                _items.Add(targetType, wrapper);
            }

            wrapper.Add(consumer);
        }

        #region Nested type: Wrapper

        private class Wrapper
        {
            private readonly List<object> _items = new List<object>();
            private readonly MethodInfo _method;

            public Wrapper(Type consumerType, Type targetType)
            {
                Type type = consumerType.MakeGenericType(targetType);
                _method = type.GetMethod("Consume", new[] {targetType});
            }

            public void Add(object consumer)
            {
                _items.Add(consumer);
            }

            /// <summary>
            /// Invoke method.
            /// </summary>
            /// <param name="value"></param>
            /// <param name="exceptionPolicy"></param>
            public void Invoke(object value, Func<Exception, bool> exceptionPolicy)
            {
                var parameters = new[] {value};
                foreach (object consumer in _items)
                {
                    try
                    {
                        _method.Invoke(consumer, parameters);
                    }
                    catch (Exception err)
                    {
                        if (!exceptionPolicy(err))
                            return;
                    }
                }
            }
        }

        #endregion
    }
}